package com.yanyeori.framework.log.common.interceptor;

import com.yanyeori.framework.log.common.annotation.LogIgnore;
import com.yanyeori.framework.log.common.configprop.LogConfigProps;
import com.yanyeori.framework.log.common.constant.LogConst;
import com.yanyeori.framework.log.common.constant.LogIgnoreType;
import com.yanyeori.framework.log.common.util.LogUtil;
import com.yanyeori.framework.log.common.wrapper.BufferedRequestWrapper;
import com.yanyeori.framework.log.common.wrapper.BufferedResponseWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.util.Collections;

/**
 * LogInterceptor
 *
 * @author chenkuan 2022/11/18
 */
@Slf4j
public class LogInterceptor implements AsyncHandlerInterceptor {

    private final LogConfigProps logConfigProps;

    public LogInterceptor(LogConfigProps logConfigProps) {
        this.logConfigProps = logConfigProps;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;

        LogIgnoreType[] types = getLogIgnoreTypes(handlerMethod);
        if (LogIgnoreType.ignore(types, LogIgnoreType.ALL)) {
            return true;
        }
        if (LogIgnoreType.ignore(types, LogIgnoreType.REQUEST)) {
            if (!LogIgnoreType.ignore(types, LogIgnoreType.RESPONSE)) {
                request.setAttribute(LogConst.METHOD_START_TIME, System.currentTimeMillis());
            }
            return true;
        }

        //请求参数
        StringBuilder params = new StringBuilder();
        String body = "";
        if (!LogIgnoreType.ignore(types, LogIgnoreType.PARAM) && request instanceof BufferedRequestWrapper) {
            //param
            for (String paramName : Collections.list(request.getParameterNames())) {
                params.append(String.format("%s=%s&", paramName, request.getParameter(paramName)));
            }
            if (params.length() > 0) {
                params.deleteCharAt(params.length() - 1);
            }
            //requestBody
            body = new String(FileCopyUtils.copyToByteArray(request.getInputStream()), StandardCharsets.UTF_8);
        }
        log.info("request: {}, path: {}, method: {}, params: {}, body: {}", LogUtil.getClientIP(request), request.getServletPath(), request.getMethod(), params, body);
        if (!LogIgnoreType.ignore(types, LogIgnoreType.RESPONSE)) {
            request.setAttribute(LogConst.METHOD_START_TIME, System.currentTimeMillis());
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;

        LogIgnoreType[] types = getLogIgnoreTypes(handlerMethod);
        if (LogIgnoreType.ignore(types, LogIgnoreType.ALL, LogIgnoreType.RESPONSE)) {
            return;
        }

        Long startTime = (Long) request.getAttribute(LogConst.METHOD_START_TIME);
        Long elapsedTime = startTime == null ? null : System.currentTimeMillis() - startTime;

        String body = "";
        if (response instanceof BufferedResponseWrapper && logConfigProps.getReqResBodyPntLength() > 0) {
            body = response.toString();
            if (StringUtils.hasLength(body)) {
                body = body.substring(0, Math.min(body.length(), logConfigProps.getReqResBodyPntLength()));
            }
        }
        log.info("response: path: {}, status: {}, time: {}, body: {}", request.getServletPath(), response.getStatus(), elapsedTime, body);
    }

    private LogIgnoreType[] getLogIgnoreTypes(HandlerMethod handlerMethod) {
        LogIgnore logIgnore = handlerMethod.getMethodAnnotation(LogIgnore.class);
        if (logIgnore == null) {
            logIgnore = handlerMethod.getBeanType().getAnnotation(LogIgnore.class);
        }
        return logIgnore == null ? null : logIgnore.type();
    }
}
